package it.uniba.di.mas.jpnde.core;

//import java.util.logging.Logger;

import jade.util.Logger;
import jade.util.leap.ArrayList;
import jade.util.leap.HashMap;
import jade.util.leap.List;
import jade.util.leap.Map;
import jade.util.leap.Serializable;

public class PN implements 
	Serializable, Runnable,Cloneable,IDeliverable{
	private static final long serialVersionUID = 1L;
	
	public static final String XMLFILE_NODE="pnml";
	public static final String XMLNODE="net";
	public static final String XMLPAGE_NODE="page";
	public static final String XMLNAME_NODE="name";
	public static final String XMLTOOL_SPEC_NODE="toolspecific";
	public static final String XMLCONTAINER_NODE="jpncontainer";
	
	public static final String STATE_READY="Ready";
	public static final String STATE_STARTING="Starting";
	public static final String STATE_RUNNING="Running";
	public static final String STATE_ERROR="Error";
	public static final String STATE_ENDED="Ended";
	
	private String id="";
	private String name="";
	private String netId="";
	private String container="";
	private ArrayList places=null;
	private ArrayList transitions=null;
	private String agentId="";
	private String state="";
	private String consoleId="";
	private Logger logger=null;
	
	private boolean initialized=false;
	private ArrayList actionListeners=new ArrayList();
	
	private boolean stop=false;
	private boolean blockRun=false;
	
	private Map placesMap=null;
	private Map placesIndexMap=null;
	
	private PNContext context=null;
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	
	public String getNetId() {
		return netId;
	}
	public void setNetId(String netName) {
		this.netId = netName;
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getContainer() {
		return container;
	}
	public void setContainer(String container) {
		this.container = container;
	}
	
	public List getPlaces(){
		return places;
	}
	public void setPlaces(ArrayList value){
		places=value;
	}
	public List getTransitions(){
		return transitions;
	}
	public void setTransitions(ArrayList value){
		transitions=value;
	}
	
	public String getAgentId() {
		return agentId;
	}
	public void setAgentId(String agentId) {
		this.agentId = agentId;
	}
	
	public String getState() {
		return state;
	}
	public void setState(String state) {
		this.state = state;
	}
	
	public String getConsoleId() {
		return consoleId;
	}
	public void setConsoleId(String consoleId) {
		this.consoleId = consoleId;
	}
	
	public synchronized void setBlockRun(boolean flgBlock){
		blockRun=flgBlock;
		if (!flgBlock)
			notifyAll();
	}
	
	public void blockRun(){
		setBlockRun(true);
	}
	
	public void unBlockRun(){
		setBlockRun(false);
	}
	
	public boolean isBlockRun(){
		return blockRun;
	}
	
	
	public boolean isDeliverated()
	{
		boolean ret=true;
		for(int i=0;i<transitions.size();i++)
			{
			if (((Transition)transitions.get(i)).getAgentId().equals(""))
				{
				ret=false;
				break;
				}
			}
		return ret;
	}
	
	public PNContext getContext(){
		return context;
	}
	
	public void initialize()
		throws Exception
	{
		context=new PNContext(getId(),agentId);
		placesMap=new HashMap();
		placesIndexMap=new HashMap();
		logger=Logger.getMyLogger(this.getClass().getName());
		
		for(int i=0;i<places.size();i++){
			Place place=(Place)places.get(i);
			place.initTokens();
			placesMap.put(place.getId(),place);
			placesIndexMap.put(place.getId(), i);
			}
		
		initialized=true;
	}
	
	private void printState()
	{
		String s="PN["+this.id+"] State: ";
		for(int i=0;i<places.size();i++){
			Place p=(Place)places.get(i);
			if (i>0) s+=";";
			s+=p.getId()+"="+p.tokensCount();
		}
		System.out.println(s);
		
		logger.log(Logger.INFO,s); 
	}
	
	public void receiveToken(Token token)
		throws Exception
	{
		if (!initialized)
			throw new Exception("PN.receiveToken(): PN <"+getId()+"> not initialized !");
		
		if (token.getEvent()==null)
			throw new Exception("PN.receiveToken(): Event not setted <"+token+">");
		
		Place place=(Place)placesMap.get(token.getEvent().getId());
		if (place==null)
			throw new Exception("PN.receiveToken(): Place not fount <"+token.getEvent().getId()+">");
		place.addToken(token);
		unBlockRun();
	}
	
	public void endTransition(Transition transition)
		throws Exception {
	if (!initialized)
		throw new Exception("PN.endTransition(): PN <"+getId()+"> not initialized !");
	
	List postset=transition.getPostset();
	for(int i=0;i<postset.size();i++){
		Arc arc=(Arc)postset.get(i);
		Place place=(Place)placesMap.get(arc.getPlaceId());
		if (place==null)
			throw new Exception("PN.endTransition(): Place not fount <"+arc.getPlaceId()+">");
		
		for(int j=0;j<arc.getWeight();j++){
			Token t=(Token)transition.getReturnToken().clone();
			if (arc.getScript()!=null&&
				arc.getScript().existFunction(ScriptFunction.ON_ADD))
				{
					List vars=new ArrayList();
					vars.add(new TaggedValue("token",t));
					arc.getScript().execute(ScriptFunction.ON_ADD,vars);
					t=(Token)arc.getScript().getReaturnValues().get("token");
				}
			if (t!=null)
				place.addToken(t);
			}
		}
	unBlockRun();
	}
	
	
	public void addActionListener(IPNActionListener listener){
		actionListeners.add(listener);
    }
	
	public void removeActionListener(IPNActionListener listener){
		actionListeners.remove(listener);
    }
	
	public void stop(){
		stop=true;
		unBlockRun();
	}
	
	@Override
	public String toString() {
		return "PN [id=" + id +", name=" + name +", netId=" + netId + ", container=" + container + ",\n places="+
			places+",\n trans="+transitions+"]";
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		if (!initialized){
			fireErrorActions("PN.run(): PN <"+getId()+"> not initialized !");
			return;
			}	
			
		while(true)
		{
			try
			{
				if (stop) break;
				ArrayList snapPlaces=clonePlaces();
				if (stop) break;
				
				// out event
				for(int i=0;i<snapPlaces.size();i++){
					Place place=(Place)snapPlaces.get(i);
					
					if (place.getEvent()!=null && place.getEvent().getType()==PlaceEvent.TYPE_OUTPUT){
						Token token=place.getOutToken();
						if (token!=null){
							System.out.println("PN.run(): PN <"+getId()+"> OutEvent <"+place.getId()+","+token+">");
							((Place)places.get(i)).removeToken(token);
							fireOutEventActions(place,token);
							}
						}
				} // out event
					
				// end state
				for(int i=0;i<snapPlaces.size();i++){
					Place place=(Place)snapPlaces.get(i);
					if (place.isInEndState()){
						System.out.println("PN.run(): PN <"+getId()+"> Ended in Place <"+place.getId()+">");
						fireEndActions();
						return;
						}
					
				} // end state
				
				for(int i=0;i<transitions.size();i++){
					boolean active=true;
					Transition t=(Transition)transitions.get(i);
					Object[] candidateTokens=new Object[snapPlaces.size()];
					
					List presetArcs=t.getSortedPreset();
					for(int j=0;j<presetArcs.size();j++){
						Arc arc=(Arc)presetArcs.get(j);
						Integer placeIndex=(Integer)placesIndexMap.get(arc.getPlaceId());
						
						if (placeIndex==null)
							throw new Exception("Index not found for Place <"+arc.getPlaceId()+">");
						Place p=(Place)snapPlaces.get(placeIndex);
						
						
						List vars=new ArrayList();
						for(int k=0;k<candidateTokens.length;k++){
							String placeId=((Place)snapPlaces.get(k)).getId();
							if (candidateTokens[k]!=null){
								List listToken=(List)candidateTokens[k];
								Token[] aToken=new Token[listToken.size()];
								for(int w=0;w<listToken.size();w++){
									aToken[w]=(Token)listToken.get(w);
									if (w==0)
										vars.add(new TaggedValue(placeId+"_token",aToken[0]));
								}
								vars.add(new TaggedValue(placeId+"_tokens",aToken));
							}
						}
						
						List tokens=p.getTokens(arc,vars);
						
						if (tokens.size()==0){
							active=false;
							break;}	
						else
							candidateTokens[placeIndex]=tokens;
					}
					
					if (active){
						List tokens=new ArrayList();
						for(int j=0;j<snapPlaces.size();j++){
							if (candidateTokens[j]!=null){
								List pTokens=(List)candidateTokens[j];
								for (int k=0;k<pTokens.size();k++){
									Token tok=(Token)pTokens.get(k);
									tokens.add(tok);
									// Rimuovo i token dalle due copie degli
									// oggetti
									Place p=(Place)snapPlaces.get(j);
									p.removeToken(tok);
									p=(Place)places.get(j);
									p.removeToken(tok);
								}
							}
						}
						fireTransitionActions(t,tokens);
					}// end active
				}
				
			}
			catch(Exception e){
				fireErrorActions("PN.run(): PN <"+getId()+"> :"+e.getMessage());
				}
		} // while
	}
	
	private synchronized ArrayList clonePlaces()
		throws Exception
	{
		while (isBlockRun()){
			try
				{
				wait();
				}
			catch(InterruptedException e)
			{
				throw new Exception("PN.clonePlaces(): PN <"+getId()+"> "+e.getMessage());
			}			
		}
		
		blockRun();
		ArrayList ret=new ArrayList();
		printState();
		for(int i=0;i<places.size();i++)
			ret.add(((Place)places.get(i)).clone());
		return ret;
	}
	
	
	private void fireErrorActions(String errorMessage){
		synchronized(this)
        {
        ArrayList listeners=(ArrayList)actionListeners.clone();
        for (int i=0;i<listeners.size();i++)
           {
           IPNActionListener listener=(IPNActionListener)listeners.get(i);
	       listener.errorAction(errorMessage);
           }
        }
	}
	
	private void fireTransitionActions(Transition trans,List tokens){
		synchronized(this)
        {
        ArrayList listeners=(ArrayList)actionListeners.clone();
        for (int i=0;i<listeners.size();i++)
           {
           IPNActionListener listener=(IPNActionListener)listeners.get(i);
	       listener.transictionAction(trans,tokens);
           }
        }
	}
	
	private void fireEndActions(){
		synchronized(this)
        {
        ArrayList listeners=(ArrayList)actionListeners.clone();
        for (int i=0;i<listeners.size();i++)
           {
           IPNActionListener listener=(IPNActionListener)listeners.get(i);
	       listener.endAction();
           }
        }
	}
	
	private void fireOutEventActions(Place place, Token token){
		synchronized(this)
        {
        ArrayList listeners=(ArrayList)actionListeners.clone();
        for (int i=0;i<listeners.size();i++)
           {
           IPNActionListener listener=(IPNActionListener)listeners.get(i);
	       listener.outEventAction(place, token);
           }
        }
	}
	
	public Object clone()
	{
		PN ret=new PN();
		ret.setId(id);
		ret.setNetId(netId);
		ret.setName(name);
		ret.setContainer(container);
		if (places!=null) 
			ret.setPlaces((ArrayList)places.clone());
		if (transitions!=null) 
			ret.setTransitions((ArrayList)transitions.clone());
		ret.setState(STATE_READY);
		return ret;
	}
}
